package com.kay.filter;

import com.kay.MethodInfo;
import org.apache.dubbo.common.constants.CommonConstants;
import org.apache.dubbo.common.extension.Activate;
import org.apache.dubbo.rpc.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

/**
 * @Description: 自定义Dubbo拦截器 实现以下逻辑
 * 1. 调用耗时计算；
 * 2. 每隔5s打印一次最近1分钟内每个方法的TP90、TP99的耗时情况
 * @Author: Kay
 * @CreateDate: 2021/9/15$ 13:40$
 * @UpdateUser: Kay
 * @UpdateDate: 2021/9/15$ 13:40$
 */
@Activate(group = {CommonConstants.CONSUMER})
public class TPFilter implements Filter, Runnable {
    private static Logger logger = LoggerFactory.getLogger(TPFilter.class);
    private static Map<String, List<MethodInfo>> requestMap = new ConcurrentHashMap<String, List<MethodInfo>>();

    // TPFilter 创建时，开启
    public TPFilter() {
        // 定义定时线程，每隔5秒  每个方法的TP90、TP99 耗时情况
        logger.info(" ---------------- 执行 TPFilter 构造方法 --------------- ");
        Executors.newSingleThreadScheduledExecutor().scheduleWithFixedDelay(this, 1, 5, TimeUnit.SECONDS);
    }

    /**
     * 统计每次调用时 方法耗时
     *
     * @param invoker
     * @param invocation
     * @return
     * @throws RpcException
     */
    @Override
    public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
        long startTime = System.currentTimeMillis(); // 开始时间

        try {
            // 执行实际逻辑
            return invoker.invoke(invocation);
        } catch (Exception e) {
            System.out.println(e.getMessage());
        } finally {
            long endTime = System.currentTimeMillis(); // 开始时间
            long takeTime = endTime - startTime; // 执行消耗用时
            String methodName = invocation.getMethodName(); // 方法名
            String serviceName = invocation.getServiceName(); // com.kay.service.DemoService 接口全路径
            String key = serviceName + methodName;
            List<MethodInfo> methodInfoList = requestMap.get(key);
            if (methodInfoList == null || methodInfoList.size() <= 0) {
                methodInfoList = new ArrayList<MethodInfo>();
            }
            methodInfoList.add(new MethodInfo(takeTime, endTime));
            requestMap.put(key, methodInfoList);
        }

        return null;
    }

    // 每隔5s打印一次最近1分钟内每个方法的TP90、TP99的耗时情况
    @Override
    public void run() {
        logger.info(Thread.currentThread().getName() + "============== 开始计算 最近1分钟内每个方法的TP90、TP99的耗时情况 ==============");
        if (requestMap != null) {
            long durableMilliseconds = 1 * 60 * 1000; // 一分钟对应的毫秒值
            Set<Map.Entry<String, List<MethodInfo>>> entrySet = requestMap.entrySet();
            for (Map.Entry<String, List<MethodInfo>> entry : entrySet) {
                String key = entry.getKey(); // 方法名
                List<MethodInfo> methodInfoList = entry.getValue();
                logger.info(Thread.currentThread().getName() + "最近1分钟内：{} =================>>> 请求总记录数{}, TP90 = {}, TP99={}",
                        key, methodInfoList.size(),
                        getTPNumber(methodInfoList, durableMilliseconds, 0.90),
                        getTPNumber(methodInfoList, durableMilliseconds, 0.99));
            }
        }
    }

    private long getTPNumber(List<MethodInfo> methodInfoList, long durableMilliseconds, double rate) {
        long expireTime = System.currentTimeMillis() - durableMilliseconds; // 指定时间段之前的毫秒 时间戳
        List<Long> temp = new ArrayList<Long>();
        if (methodInfoList != null && methodInfoList.size() > 0) {
            try {
                Iterator<MethodInfo> iterator = methodInfoList.iterator();
                while (iterator.hasNext()) {
                    MethodInfo methodInfo = iterator.next();
                    if (methodInfo.getCreateAt() >= expireTime) {
                        // 时间范围之内
                        temp.add(methodInfo.getTakeTime());
                    } else {
                        // 超过指定时间 被移除
                        iterator.remove();
                    }
                }
            }catch (Exception e){

            }

        }
        Collections.sort(temp);

        // TP90：指在一个时间段内（如题目中的1分钟），统计该方法每次调用所消耗的时间，并将这些时间按从小到大的顺序进行排序，第90%的那个值作为TP90值；
        return temp.get((int) Math.round(temp.size() * rate) - 1); // 第一个为 0 需要 -1
    }
}
